Skip to content

feat: adds trash support (soft deletes) #12656

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 54 commits into
base: main
Choose a base branch
from
Open

Conversation

PatrikKozak
Copy link
Contributor

@PatrikKozak PatrikKozak commented Jun 2, 2025

What?

This PR introduces complete trash (soft-delete) support. When a collection is configured with trash: true, documents can now be soft-deleted and restored via both the API and the admin panel.

import type { CollectionConfig } from 'payload'

const Posts: CollectionConfig = {
  slug: 'posts',
  trash: true, // <-- New collection config prop @default false
  fields: [
    {
      name: 'title',
      type: 'text',
    },
    // other fields...
  ],
}

Why

Soft deletes allow developers and admins to safely remove documents without losing data immediately. This enables workflows like reversible deletions, trash views, and auditing—while preserving compatibility with drafts, autosave, and version history.

How?

Backend

  • Adds new trash: true config option to collections.
  • When enabled:
    • A deletedAt timestamp is conditionally injected into the schema.
    • Soft deletion is performed by setting deletedAt instead of removing the document from the database.
  • Extends all relevant API operations (find, findByID, update, delete, versions, etc.) to support a new trash param:
    • trash: false → excludes trashed documents (default)
    • trash: true → includes both trashed and non-trashed documents
    • To query only trashed documents: use trash: true with a where clause like { deletedAt: { exists: true } }
  • Enforces delete access control before allowing a soft delete via update or updateByID.
  • Disables version restoring on trashed documents (must be restored first).

Admin Panel

  • Adds a dedicated Trash view: /collections/:collectionSlug/trash
  • Default delete action now soft-deletes documents when trash: true is set.
  • Delete confirmation modal includes a checkbox to permanently delete instead.
  • Trashed documents:
    • Render in a read-only edit view
    • Still allow access to Preview, API, and Versions tabs
  • Adds new Restore bulk action to clear the deletedAt timestamp.
  • Adds Empty Trash and Delete permanently bulk actions.

Notes

  • This feature is completely opt-in. Collections without trash: true behave exactly as before.
Screen.Recording.2025-07-08.at.11.56.19.AM.mov

@PatrikKozak PatrikKozak marked this pull request as ready for review June 6, 2025 17:13
@JarrodMFlesch
Copy link
Contributor

JarrodMFlesch commented Jun 7, 2025

Feels like we should add some tests for docs that were soft deleted and then moved out of trash. Looks like null is the value we are using.

  • User should be able to bring doc out of trash
  • Should be able to still query docs that have null set as the value (versions and normal docs)

@PatrikKozak PatrikKozak changed the title feat: soft deletes feat: add trash support (soft deletes) Jun 23, 2025
Copy link
Contributor

@DanRibbens DanRibbens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor code cleanup, looks good otherwise.

@PatrikKozak PatrikKozak changed the title feat: add trash support (soft deletes) feat: adds trash support (soft deletes) Jul 1, 2025
Required for #12656.

### What

This PR introduces full admin UI support for Payload’s new soft delete
(`trash`) functionality. When a collection has `trash: true`, documents
can now be soft-deleted from the admin panel, viewed in a dedicated
“Trash” view, and optionally restored.

### How

- Trash View:
Adds a new route at `/collections/:collectionSlug/trash` to view and
manage soft-deleted documents.

- Delete Behavior:
On collections with `trash: true`, deleting a document from the admin UI
will soft delete it by default (setting the `deletedAt` timestamp).
A new checkbox in the delete confirmation modal allows for permanent
deletion (bypassing trash) if desired.

- Restore Action:
Soft-deleted documents can be restored by clearing the `deletedAt`
timestamp via a new “Restore” button.

- Trashed Document Behavior:
When viewing a trashed document, the UI renders in a fully read-only
state.
However, users can still navigate to the **API**, **Preview**, and
**Versions** tabs from the edit view to inspect or interact with those
surfaces.

- Access Control Integration:
All trash-related actions respect the collection’s delete access
control.

### Notes

- This builds on the backend soft delete support introduced in a
separate PR.

- UI behavior integrates cleanly with autosave, drafts, and version
history.

- Designed to be unobtrusive for collections that do not enable trash.
Copy link
Contributor

github-actions bot commented Jul 8, 2025

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 750.25 KB ⚠️ +2.03 KB (+0.3%)
packages/payload/meta_index.json esbuild/index.js 1.21 MB ⚠️ +1.71 KB (+0.1%)
packages/payload/meta_shared.json esbuild/exports/shared.js 160.56 KB ✅ No change
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 259.22 KB ✅ No change
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 1.13 MB ⚠️ +7.32 KB (+0.6%)
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 14.10 KB ✅ No change
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████▏ }}}$ 80.7%, 601.90 KB
dist/views/Version ${{\color{Goldenrod}{ █▋ }}}$ 6.7%, 49.63 KB
dist/views/Document ${{\color{Goldenrod}{ ▌ }}}$ 2.0%, 14.84 KB
dist/views/Root ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 7.08 KB
dist/views/Versions ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 6.30 KB
dist/views/List ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 6.24 KB
dist/views/API ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 5.98 KB
dist/views/Account ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 5.33 KB
dist/elements/Nav ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 4.84 KB
dist/elements/DocumentHeader ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 4.67 KB
dist/views/Login ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 4.38 KB
dist/views/Dashboard ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 3.68 KB
dist/views/ForgotPassword ${{\color{Goldenrod}{ }}}$ 0.4%, 3.09 KB
dist/layouts/Root ${{\color{Goldenrod}{ }}}$ 0.4%, 3.09 KB
dist/utilities/initPage ${{\color{Goldenrod}{ }}}$ 0.4%, 3.06 KB
dist/templates/Default ${{\color{Goldenrod}{ }}}$ 0.4%, 2.83 KB
dist/views/BrowseByFolder ${{\color{Goldenrod}{ }}}$ 0.3%, 2.59 KB
dist/views/CreateFirstUser ${{\color{Goldenrod}{ }}}$ 0.3%, 2.48 KB
dist/views/CollectionFolders ${{\color{Goldenrod}{ }}}$ 0.3%, 2.46 KB
dist/views/ResetPassword ${{\color{Goldenrod}{ }}}$ 0.3%, 2.41 KB
(other) ${{\color{Goldenrod}{ ████▊ }}}$ 19.3%, 143.67 KB

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ █████████████████▌ }}}$ 70.1%, 841.06 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▊ }}}$ 3.3%, 40.09 KB
dist/collections/operations ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 34.48 KB
dist/auth/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 14.57 KB
dist/queues/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 11.92 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 11.54 KB
dist/globals/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 11.03 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 10.82 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 9.35 KB
dist/bin/generateImportMap ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.27 KB
dist/database/migrations ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.79 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.78 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.74 KB
dist/collections/endpoints ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.17 KB
dist/index.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.07 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 6.25 KB
dist/config/sanitize.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 5.44 KB
dist/auth/endpoints ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 5.41 KB
dist/utilities/telemetry ${{\color{Goldenrod}{ }}}$ 0.4%, 5.31 KB
dist/auth/strategies ${{\color{Goldenrod}{ }}}$ 0.4%, 4.68 KB
(other) ${{\color{Goldenrod}{ ███████▍ }}}$ 29.9%, 358.50 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████ }}}$ 80.2%, 126.19 KB
dist/fields/validations.js ${{\color{Goldenrod}{ █▍ }}}$ 5.9%, 9.36 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 2.79 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▍ }}}$ 1.6%, 2.48 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 1.41 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 1.28 KB
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 943 B
dist/utilities/fieldSchemaToJSON.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 917 B
dist/folders/utils ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 881 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 779 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 713 B
dist/collections/config ${{\color{Goldenrod}{ }}}$ 0.4%, 570 B
dist/bin/generateImportMap ${{\color{Goldenrod}{ }}}$ 0.4%, 559 B
dist/utilities/getSafeRedirect.js ${{\color{Goldenrod}{ }}}$ 0.3%, 423 B
dist/utilities/deepMerge.js ${{\color{Goldenrod}{ }}}$ 0.3%, 413 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.2%, 391 B
dist/utilities/formatLabels.js ${{\color{Goldenrod}{ }}}$ 0.2%, 380 B
dist/utilities/mergeListSearchAndWhere.js ${{\color{Goldenrod}{ }}}$ 0.2%, 344 B
dist/utilities/getBestFitFromSizes.js ${{\color{Goldenrod}{ }}}$ 0.2%, 340 B
(other) ${{\color{Goldenrod}{ ████▉ }}}$ 19.8%, 31.18 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/lexical/plugins ${{\color{Goldenrod}{ ███ }}}$ 12.1%, 30.91 KB
dist/features/experimental_table ${{\color{Goldenrod}{ ██▍ }}}$ 9.6%, 24.60 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▎ }}}$ 9.2%, 23.47 KB
dist/features/blocks ${{\color{Goldenrod}{ ██▏ }}}$ 8.8%, 22.43 KB
dist/packages/@lexical ${{\color{Goldenrod}{ █▊ }}}$ 7.4%, 18.99 KB
dist/features/link ${{\color{Goldenrod}{ █▊ }}}$ 7.0%, 17.96 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▋ }}}$ 6.8%, 17.44 KB
dist/features/textState ${{\color{Goldenrod}{ █ }}}$ 4.3%, 10.98 KB
dist/features/upload ${{\color{Goldenrod}{ ▉ }}}$ 3.7%, 9.60 KB
dist/features/relationship ${{\color{Goldenrod}{ ▉ }}}$ 3.7%, 9.41 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 8.08 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 7.34 KB
dist/features/converters ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 7.04 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 7.01 KB
dist/lexical/config ${{\color{Goldenrod}{ ▌ }}}$ 2.0%, 5.10 KB
dist/features/lists ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 4.95 KB
dist/lexical/theme ${{\color{Goldenrod}{ ▍ }}}$ 1.6%, 4.01 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 3.46 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 3.23 KB
dist/features/indent ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 2.50 KB
(other) ${{\color{Goldenrod}{ █████████████████████▉ }}}$ 87.9%, 225.29 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████▋ }}}$ 50.9%, 572.93 KB
dist/elements/FolderView ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 30.36 KB
dist/elements/BulkUpload ${{\color{Goldenrod}{ ▌ }}}$ 2.2%, 24.52 KB
dist/elements/WhereBuilder ${{\color{Goldenrod}{ ▍ }}}$ 1.6%, 17.94 KB
dist/fields/Relationship ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 15.93 KB
dist/elements/Table ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 15.21 KB
dist/views/Edit ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 15.01 KB
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 14.21 KB
dist/fields/Blocks ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 12.37 KB
dist/fields/Upload ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 11.94 KB
dist/fields/Array ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 9.29 KB
dist/elements/ListControls ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 9.03 KB
dist/elements/PublishButton ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 8.57 KB
dist/providers/Folders ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.38 KB
dist/elements/LivePreview ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.19 KB
dist/elements/ListHeader ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.83 KB
dist/elements/HTMLDiff ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.81 KB
dist/views/CollectionFolder ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.74 KB
dist/views/List ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.41 KB
dist/elements/ReactSelect ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.91 KB
(other) ${{\color{Goldenrod}{ ████████████▎ }}}$ 49.1%, 552.33 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ █████▊ }}}$ 23.1%, 3.12 KB
../../node_modules ${{\color{Goldenrod}{ ████▉ }}}$ 19.6%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██▊ }}}$ 11.3%, 1.52 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ ██▍ }}}$ 9.8%, 1.32 KB
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █▌ }}}$ 6.0%, 814 B
dist/providers/TableColumns ${{\color{Goldenrod}{ █▍ }}}$ 5.6%, 757 B
dist/utilities/api.js ${{\color{Goldenrod}{ █▍ }}}$ 5.6%, 756 B
dist/elements/Translation ${{\color{Goldenrod}{ ▉ }}}$ 3.7%, 493 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 339 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 251 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 232 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 168 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 159 B
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 147 B
dist/utilities/abortAndIgnore.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 146 B
dist/utilities/hasSavePermission.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 136 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 129 B
dist/utilities/findLocaleFromCode.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 84 B
dist/utilities/sanitizeID.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 77 B
dist/utilities/isEditing.js ${{\color{Goldenrod}{ }}}$ 0.4%, 59 B
(other) ${{\color{Goldenrod}{ ███████████████████▏ }}}$ 76.9%, 10.38 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.

DanRibbens
DanRibbens previously approved these changes Jul 9, 2025
@rilrom
Copy link
Contributor

rilrom commented Jul 10, 2025

This is looking amazing, great job.

Once this is marked as stable I'll make sure to move my soft delete plugin into maintenance mode and point users towards this feature instead.

@PP-Tom
Copy link

PP-Tom commented Jul 14, 2025

Looking forward to this one!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants